package com.db.stexchem.serialization;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.mockito.exceptions.Reporter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.db.stexchem.message.ExecutionReport;
import com.db.stexchem.message.LoginRequest;
import com.db.stexchem.message.LoginResponse;
import com.db.stexchem.message.Message;
import com.db.stexchem.message.MessageType;
import com.db.stexchem.message.Operation;
import com.db.stexchem.message.Order;
import com.db.stexchem.message.PlaceOrderResponse;
import com.db.stexchem.message.Status;
import com.db.stexchem.protocol.Common.ProtoBufStatus;
import com.db.stexchem.protocol.Login.ProtoBufLoginRequest;
import com.db.stexchem.protocol.Login.ProtoBufLoginResponse;
import com.db.stexchem.protocol.Message.ProtoBufMessage;
import com.db.stexchem.protocol.Notification.ProtoBufExecutionReport;
import com.db.stexchem.protocol.PlaceOrder.ProtoBufPlaceOrderRequest;
import com.db.stexchem.protocol.Common.ProtoBufOperation;
import com.db.stexchem.protocol.PlaceOrder.ProtoBufPlaceOrderResponse;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;

public class ProtoBufSerializerImpl implements Serializer {
  
  private final Logger        logger               = LoggerFactory
                                                       .getLogger(ProtoBufSerializerImpl.class);
  private final static String PLACE_ORDER_RESPONSE = "place order response";
  private final static String EXECUTION_REPORT     = "execution report";
  
  public ProtoBufSerializerImpl() {
  }
  
  @Override
  public Message deserializeMessage(InputStream inputStream) throws IOException {
    logger.debug("trying to deserialize message ...");
    ProtoBufMessage message = ProtoBufMessage.parseDelimitedFrom(inputStream);
    if (message == null) {
      throw new IOException("failed to deserialize message");
    }
    switch (message.getId()) {
    case PLACE_ORDER_RESPONSE:
      return deserializePlaceOrderResponse(inputStream, message.getData());
    case EXECUTION_REPORT:
      return deserializeExecutionReport(inputStream, message.getData());
    default:
      throw new IOException("unknown message id");
    }
    
  }
  
  private Message deserializePlaceOrderResponse(InputStream inputStream,
      ByteString data) throws InvalidProtocolBufferException {
    logger.debug("trying to deserialize place order response ...");
    Message message = new Message();
    message.setMessageType(MessageType.PLACE_ORDER_RESPONSE);
    
    ProtoBufPlaceOrderResponse response = ProtoBufPlaceOrderResponse
        .parseFrom(data);
    
    PlaceOrderResponse placeOrderResponse = new PlaceOrderResponse();
    placeOrderResponse
        .setStatus(response.getStatus() == ProtoBufStatus.OK ? Status.OK
            : Status.BAD_REQUEST);
    placeOrderResponse.setErrorMessage(response.hasErrorMessage() ? response
        .getErrorMessage() : null);
    placeOrderResponse.setOrderNo(response.hasOrderNo() ? new Integer(response
        .getOrderNo()) : null);
    
    message.setData(placeOrderResponse);
    logger.debug("{} has been successfully deserialized", placeOrderResponse);
    return message;
  }
  
  private Message deserializeExecutionReport(InputStream inputStream,
      ByteString data) throws InvalidProtocolBufferException {
    logger.debug("trying to deserialize execution report ...");
    Message message = new Message();
    message.setMessageType(MessageType.EXECUTION_REPORT);
    
    ProtoBufExecutionReport report = ProtoBufExecutionReport.parseFrom(data);
    
    ExecutionReport executionReport = new ExecutionReport();
    executionReport.setFullyFilled(report.hasFullyFilled() ? report
        .getFullyFilled() : true);
    executionReport.setOrderNo(report.getOrderNo());
    executionReport.setNShares(report.getNShares());
    executionReport.setPrice(report.getPrice());
    executionReport.setCounterparty(report.getCounterparty());
    executionReport.setStockName(report.getStockName());
    executionReport.setOperation(report.getOperation() == ProtoBufOperation.SELL ?
        Operation.SELL : Operation.BUY);
    
    message.setData(executionReport);
    logger.debug("{} has been successfully deserialized", executionReport);
    return message;
  }
  
  @Override
  public void serialize(OutputStream outputStream, LoginRequest loginRequest)
      throws IOException {
    logger.debug("trying to serialize {} ...", loginRequest);
    ProtoBufLoginRequest.newBuilder().setLoginName(loginRequest.getLoginName())
        .setPassword(loginRequest.getPassword()).build()
        .writeDelimitedTo(outputStream);
    logger.debug("login request has been successfully serialized");
  }
  
  @Override
  public LoginResponse deserializeLoginResponse(InputStream inputStream)
      throws IOException {
    logger.debug("trying to deserialize login response ...");
    ProtoBufLoginResponse response = ProtoBufLoginResponse
        .parseDelimitedFrom(inputStream);
    if (response == null) {
      throw new IOException("failed to deserialize login response");
    }
    LoginResponse loginResponse = new LoginResponse();
    loginResponse
        .setStatus(response.getStatus() == ProtoBufStatus.OK ? Status.OK
            : Status.BAD_REQUEST);
    loginResponse.setErrorMessage(response.hasErrorMessage() ? response
        .getErrorMessage() : null);
    logger.debug("{} has been successfully deserialized", loginResponse);
    return loginResponse;
  }
  
  @Override
  public void serialize(OutputStream outputStream, Order order)
      throws IOException {
    logger.debug("trying to serialize {} ...", order);
    ProtoBufPlaceOrderRequest.Builder builder = ProtoBufPlaceOrderRequest
        .newBuilder()
        .setStockName(order.getStockName())
        .setOperation(
            order.getOperation() == Operation.SELL ? ProtoBufOperation.SELL
                : ProtoBufOperation.BUY)
        .setNShares(order.getNShares());
    
    if (order.getPrice() != null) {
      builder.setPrice(order.getPrice());
    }
    
    builder.build().writeDelimitedTo(outputStream);
    logger.debug("order has been successfully serialized");
  }
  
  @Override
  public LoginRequest deserializeLoginRequest(InputStream inputStream)
      throws IOException {
    logger.debug("trying to deserialize login request ...");
    ProtoBufLoginRequest request = ProtoBufLoginRequest
        .parseDelimitedFrom(inputStream);
    if (request == null) {
      throw new IOException("failed to deserialize login request");
    }
    LoginRequest loginRequest = new LoginRequest();
    loginRequest.setLoginName(request.getLoginName());
    loginRequest.setPassword(request.getPassword());
    logger.debug("{} has been successfully deserialized", loginRequest);
    return loginRequest;
  }
  
  @Override
  public void serialize(OutputStream outputStream, LoginResponse loginResponse)
      throws IOException {
    logger.debug("trying to serialize login {} ...", loginResponse);
    ProtoBufLoginResponse.Builder builder = ProtoBufLoginResponse.newBuilder()
        .setStatus(
            loginResponse.getStatus() == Status.OK ? ProtoBufStatus.OK
                : ProtoBufStatus.BAD_REQUEST);
    
    if (loginResponse.getErrorMessage() != null) {
      builder.setErrorMessage(loginResponse.getErrorMessage());
    }
    
    builder.build().writeDelimitedTo(outputStream);
    logger.debug("login response has been successfully serialized");
  }
  
  @Override
  public Order deserializeOrder(InputStream inputStream) throws IOException {
    logger.debug("trying to deserialize order ...");
    ProtoBufPlaceOrderRequest request = ProtoBufPlaceOrderRequest
        .parseDelimitedFrom(inputStream);
    if (request == null) {
      throw new IOException("failed to deserialize order");
    }
    Order order = new Order();
    order.setStockName(request.getStockName());
    order
        .setOperation(request.getOperation() == ProtoBufOperation.SELL ? Operation.SELL
            : Operation.BUY);
    order.setNShares(request.getNShares());
    if (request.hasPrice()) {
      order.setPrice(request.getPrice());
    }
    logger.debug("{} has been successfully deserialized", order);
    return order;
  }
  
  @Override
  public void serialize(OutputStream outputStream,
      PlaceOrderResponse placeOrderResponse) throws IOException {
    logger.debug("trying to serialize {} ...", placeOrderResponse);
    ProtoBufPlaceOrderResponse.Builder builder = ProtoBufPlaceOrderResponse
        .newBuilder().setStatus(
            placeOrderResponse.getStatus() == Status.OK ? ProtoBufStatus.OK
                : ProtoBufStatus.BAD_REQUEST);
    
    if (placeOrderResponse.getErrorMessage() != null) {
      builder.setErrorMessage(placeOrderResponse.getErrorMessage());
    }
    if (placeOrderResponse.getOrderNo() != null) {
      builder.setOrderNo(placeOrderResponse.getOrderNo());
    }
    
    ProtoBufMessage.newBuilder().setId(PLACE_ORDER_RESPONSE)
        .setData(builder.build().toByteString()).build()
        .writeDelimitedTo(outputStream);
    logger.debug("place order response has been successfully serialized");
  }
  
  @Override
  public void serialize(OutputStream outputStream,
      ExecutionReport executionReport) throws IOException {
    logger.debug("trying to serialize {} ...", executionReport);
    ProtoBufExecutionReport.Builder builder = ProtoBufExecutionReport
        .newBuilder().setFullyFilled(executionReport.isFullyFilled())
        .setOrderNo(executionReport.getOrderNo())
        .setNShares(executionReport.getNShares())
        .setPrice(executionReport.getPrice())
        .setCounterparty(executionReport.getCounterparty())
        .setStockName(executionReport.getStockName())
        .setOperation(executionReport.getOperation() == Operation.BUY ?
            ProtoBufOperation.BUY : ProtoBufOperation.SELL);
    
    ProtoBufMessage.newBuilder().setId(EXECUTION_REPORT)
        .setData(builder.build().toByteString()).build()
        .writeDelimitedTo(outputStream);
    logger.debug("execution report has been successfully serialized");
  }
  
}
